﻿using System;
using System.Collections.Generic;
using System.Drawing;

namespace NNImage
{
    public class Neuron
    {
        #region Pola prywatne
        private Point _koordynaty;
        private double _alfa;
        private double _sigma;
        private double _tau1;
        private double _tau2;
        private double _potencjal;
        private double _potencjalMinimalny;
        private byte[] _wagi;
        private IFunkcjaOdleglosci _funkcjaOdleglosci;
        private IFunkcjaSasiedztwa _funkcjaSasiedztwa;
        private bool _czyZwycieski;
        #endregion

        #region Konstruktor
        /// <summary>
        /// Konstruktor.
        /// </summary>
        /// <param name="x">Współrzędna X neuronu</param>
        /// <param name="y">Wspołrzędna Y neuronu</param>
        /// <param name="rozmiarWektora">Rozmiar wektora wejściowego (ramki)</param>
        /// <param name="funkcjaOdleglosci">Zastosowana miara odległości</param>
        /// <param name="funkcjaSasiedztwa">Zastosowana miara sąsiedztwa między neuronami</param>
        /// <param name="potencjalMinimalny">Minimalny potencjał, który musi posiadać neuron, aby uczestniczyć w wyścigu</param>
        /// <param name="alfa">Współczynnik uczenia alfa</param>
        /// <param name="sigma">Promień sąsiedztwa sigma</param>
        public Neuron(int x, int y, int rozmiarWektora, IFunkcjaOdleglosci funkcjaOdleglosci, IFunkcjaSasiedztwa funkcjaSasiedztwa, double potencjalMinimalny, double alfa, double sigma)
        {
            _koordynaty.X = x;
            _koordynaty.Y = y;
            _funkcjaOdleglosci = funkcjaOdleglosci;
            _funkcjaSasiedztwa = funkcjaSasiedztwa;
            _wagi = new byte[rozmiarWektora];
            _alfa = alfa;
            _sigma = sigma;
            _potencjalMinimalny = potencjalMinimalny;
            _potencjal = 1;
            _czyZwycieski = false;
            _tau2 = 1000;
            _tau1 = 1000 / Math.Log(_sigma);
        }
        #endregion

        #region Właściwości
        public double Alfa(int iteracja)
        {
            return _alfa * Math.Exp(-iteracja / _tau2);
        }

        public double Sigma(int iteracja)
        {
            return _sigma * Math.Exp(-iteracja / _tau1);
        }

        public bool CzyZwycieski
        {
            get
            {
                return _czyZwycieski;
            }

            set
            {
                _czyZwycieski = value;
            }
        }

        public Point Koordynaty
        {
            get
            {
                return _koordynaty;
            }
        }

        public byte[] Wagi
        {
            get
            {
                return _wagi;
            }
        }
        #endregion

        #region Metody
        /// <summary>
        /// Metoda losująca początkowe wagi dla neuronu.
        /// </summary>
        /// <param name="min">Dolna granica zakresu losowania</param>
        /// <param name="max">Górna granica zakresu losowania</param>
        /// <param name="ziarno">Ziarno losujące</param>
        public void LosujWagi(int min, int max, Random ziarno)
        {
            if ((ziarno == null) || (max <= min))
            { throw (new Exception("Nieprawidłowe parametry losujące")); };

            for (int i = 0; i < _wagi.Length; ++i)
            {
                _wagi[i]=((byte)ziarno.Next(min, max));
            }

        }

        /// <summary>
        /// Metoda zwracająca odległość między wektorem wejściowym a wektorem wag.
        /// </summary>
        /// <param name="wektorWejsciowy">Aktualnie rozpatrywana ramka</param>
        /// <returns></returns>
        public double Oblicz(List<byte> wektorWejsciowy)
        {
            return _funkcjaOdleglosci.Oblicz(wektorWejsciowy, _wagi);
        }

        /// <summary>
        /// Metoda korygująca wagi danego neuronu.
        /// </summary>
        /// <param name="wektorWejsciowy">Wektor aktualnie rozpatrywanej ramki</param>
        /// <param name="zwyciezca">Koordynaty zwycięskiego neuronu</param>
        /// <param name="iteracja">Numer aktualnej iteracji</param>
        public void KorygujWagi(List<byte> wektorWejsciowy, Point zwyciezca, int iteracja)
        {
            double temp;
            double odleglosc = _funkcjaSasiedztwa.Oblicz(this._koordynaty, zwyciezca, Sigma(iteracja));
            if (odleglosc <= 0) return;
            double alfa = Alfa(iteracja);
            for (int i = 0; i < _wagi.Length; ++i)
            {
                temp = alfa * odleglosc * Math.Abs(wektorWejsciowy[i] - _wagi[i]);
                _wagi[i] += Convert.ToByte(temp);
            }
        }

        /// <summary>
        /// Metoda korygująca potencjał neuronu po każdej iteracji.
        /// </summary>
        /// <param name="neurony">Ilość neuronów w sieci</param>
        public void KorygujPotencjal(int neurony)
        {
            if (_czyZwycieski)
            {
                _potencjal -= _potencjalMinimalny;
                _czyZwycieski = false;
            }
            else
            {
                _potencjal += 1 / neurony;

            }
        }
        #endregion
    }
}
